﻿using System;
using System.Linq;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements.CWNotation
{
    /// <summary>
    /// Represents one barline segment.
    /// </summary>
    /// <remarks>
    /// The barline segments are part of a common western notation <see cref="PartDescriptorElement"/> extension.
    /// They define how a barline (in how many segments) is drawn on this part.<br/>
    /// A segment is given by its upper and lower staff.
    /// </remarks>
    public class BarlineSegment : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="BarlineSegment"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="CWPartData"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="upperStaff">
        /// <para>
        /// Type: <see cref="StaffDescriptor"/>
        /// </para>
        /// <para>
        /// The upper one of the staves connected by this segment.
        /// </para>
        /// </param>
        /// <param name="lowerStaff">
        /// <para>
        /// Type: <see cref="StaffDescriptor"/>
        /// </para>
        /// <para>
        /// The lower one of the staves connected by this segment.
        /// This value must not be <see langword="null"/>. However, it can be the same as <paramref name="upperStaff"/>
        /// to express a segment that is drawn on just one staff.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ValidationException">
        /// The <paramref name="upperStaff"/> or <paramref name="lowerStaff"/> does not belong to the same <see cref="PartDescriptorElement"/>
        /// as this segment.
        /// </exception>
        internal BarlineSegment(XmlPackagePart packagePart, CWPartData owner, StaffDescriptor upperStaff, StaffDescriptor lowerStaff)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");
            if (upperStaff == null)
                throw new ArgumentNullException("upperStaff");
            if (lowerStaff == null)
                throw new ArgumentNullException("lowerStaff");

            LowerStaff = lowerStaff;
            UpperStaff = upperStaff;
        }

        /// <summary>
        /// Creates a new <see cref="BarlineSegment"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="CWPartData"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal BarlineSegment(XmlPackagePart packagePart, CWPartData owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            lowerStaffAttribute = element.Attributes[PackageVocabulary.LowerStaffAttribute];
            upperStaffAttribute = element.Attributes[PackageVocabulary.UpperStaffAttribute];

            var lsid = new Guid(lowerStaffAttribute.Value);
            var usid = new Guid(upperStaffAttribute.Value);

            upperStaff = FindOwner<CWPartData>().Staves.FirstOrDefault(s => s.ID == usid);
            lowerStaff = FindOwner<CWPartData>().Staves.FirstOrDefault(s => s.ID == lsid);

            if (upperStaff == null)
                throw new SerializationException("The staff ID " + usid + " was not found in the part descriptor!", element.OuterXml);
            if (lowerStaff == null)
                throw new SerializationException("The staff ID " + lsid + " was not found in the part descriptor!", element.OuterXml);
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.BarlineSegmentElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            upperStaffAttribute = document.CreateAttribute(PackageVocabulary.UpperStaffAttribute);
            lowerStaffAttribute = document.CreateAttribute(PackageVocabulary.LowerStaffAttribute);

            element.Attributes.Append(upperStaffAttribute);
            element.Attributes.Append(lowerStaffAttribute);

            return element;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the lower staff of the segment.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="StaffDescriptor"/>
        /// </para>
        /// <para>
        /// The descriptor of the lower staff of the segment.
        /// </para>
        /// <para>
        /// To express a segment that is drawn on a single staff, set both <see cref="LowerStaff"/>
        /// and <see cref="UpperStaff"/> to the same descriptor instance.
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// The <paramref name="value"/> is <see langword="null"/>.<br/>
        /// -or-
        /// The <paramref name="value"/> belongs to another <see cref="PartDescriptorElement"/> than this segment.
        /// </exception>
        /// TODO: Write the validation rules.
        public StaffDescriptor LowerStaff
        {
            get { return lowerStaff; }
            set
            {
                ValidateAndThrow("LowerStaff", value);

                lowerStaff = value;
                lowerStaffAttribute.Value = lowerStaff.ID.ToString();

                RaisePropertyChanged("LowerStaff");
            }
        }

        /// <summary>
        /// Gets or sets the upper staff of the segment.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="StaffDescriptor"/>
        /// </para>
        /// <para>
        /// The descriptor of the upper staff of the segment.
        /// </para>
        /// <para>
        /// To express a segment that is drawn on a single staff, set both <see cref="LowerStaff"/>
        /// and <see cref="UpperStaff"/> to the same descriptor instance.
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// The <paramref name="value"/> is <see langword="null"/>.<br/>
        /// -or-
        /// The <paramref name="value"/> belongs to another <see cref="PartDescriptorElement"/> than this segment.
        /// </exception>
        /// TODO: Write the validation rules.
        public StaffDescriptor UpperStaff
        {
            get { return upperStaff; }
            set
            {
                ValidateAndThrow("UpperStaff", value);

                upperStaff = value;
                upperStaffAttribute.Value = upperStaff.ID.ToString();

                RaisePropertyChanged("UpperStaff");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="LowerStaff"/> property.
        /// </summary>
        private StaffDescriptor lowerStaff;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="LowerStaff"/> property.
        /// </summary>
        private XmlAttribute lowerStaffAttribute;
        /// <summary>
        /// Holds the value of the <see cref="UpperStaff"/> property.
        /// </summary>
        private StaffDescriptor upperStaff;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="UpperStaff"/> property.
        /// </summary>
        private XmlAttribute upperStaffAttribute;

        #endregion
    }
}
